package com.mjk.common.tools.lambada;


import com.mjk.common.tools.logger.BizLogger;
import com.mjk.common.util.ReflectionUtils;

import java.lang.invoke.*;
import java.lang.reflect.Method;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * lambda 替换放射调用工具类 用于提升调用性能
 * 
 * @author Administrator
 *
 */
public class LambdaReflectionUtil {
	
	public static Object invoke(Object instance, String methodName,Object obj) {
		Method method = ReflectionUtils.getMethodByName(instance.getClass(), methodName);
		if(null == method) {
			return null;
		}
		if(method.getReturnType().isAssignableFrom(void.class)) {//如果没有返回值
			if(method.getParameterCount()>0) {//有参数
				invokeWithArgNoReturn(instance, method, obj);
			}else {
				invokeNoArgNoReturn(instance, method);
			}
			return null;
		}else {//有返回值
			if(method.getParameterCount()>0) {//有参数
				return invokeWithArgWithReturn(instance, method, obj);
			}else {
				return invokeNoArgWithReturn(instance, method);
			}
		}
	}
	
	
	/**
	 * 调用无参 无返回值
	 */
	private static void invokeNoArgNoReturn(Object instance, Method method) {
		MethodHandles.Lookup lookup = MethodHandles.lookup();
		try {
			MethodHandle methodHandle = lookup.unreflect(method);
			MethodType invokedType = MethodType.methodType(Consumer.class);
			MethodType returnType = MethodType.methodType(void.class, instance.getClass());
			CallSite operate = LambdaMetafactory.metafactory(lookup, "accept", invokedType,
					MethodType.methodType(void.class, Object.class), methodHandle, returnType);
			Consumer sp = (Consumer) operate.getTarget().invoke();
			sp.accept(instance); // 传入 int 参数
		} catch (Throwable e) {
			BizLogger.error(e);
		}
	}

	/**
	 * 调用有参 无返回值
	 */
	private static void invokeWithArgNoReturn(Object instance, Method method,Object value) {
		MethodHandles.Lookup lookup = MethodHandles.lookup();
		try {
			  MethodHandle methodHandle = lookup.unreflect(method);
	            MethodType invokedType = MethodType.methodType(BiConsumer.class);
	            MethodType returnType = MethodType.methodType(void.class, instance.getClass(),method.getParameterTypes()[0]);
	            CallSite operate = LambdaMetafactory.metafactory(
	                    lookup,
	                    "accept",
	                    invokedType,
	                    MethodType.methodType(void.class, Object.class, Object.class),
	                    methodHandle,
	                    returnType
	            );
			BiConsumer<Object,Object> sp = (BiConsumer<Object,Object>) operate.getTarget().invoke();
			sp.accept(instance, value); // 传入 int 参数
		} catch (Throwable e) {
			BizLogger.error(e);
		}
	}

	/**
	 * 调用无参有返回值
	 *
     */
	private static Object invokeNoArgWithReturn(Object instance, Method method) {
		MethodHandles.Lookup lookup = MethodHandles.lookup();
		try {
			MethodType invokedType = MethodType.methodType(Function.class);
			MethodType returnType = MethodType.methodType(Object.class, instance.getClass());
			MethodType implMethod = MethodType.methodType(Object.class, Object.class);
			MethodHandle methodHandle = lookup.unreflect(method);
			CallSite operate = LambdaMetafactory.metafactory(lookup, "apply", invokedType, implMethod, methodHandle,
					returnType);
			Function sp = (Function) operate.getTarget().invoke();
			return sp.apply(instance);
		} catch (Throwable e) {
			throw BizLogger.error(e);
		}
	}
	/**
	 * 调用有参数有返回值
     */
	private static Object invokeWithArgWithReturn(Object instance, Method method,Object value) {
		MethodHandles.Lookup lookup = MethodHandles.lookup();
		try {
			MethodType  invokedType = MethodType.methodType(BiFunction.class);
			MethodType  returnType = MethodType.methodType(Object.class,instance.getClass(),method.getParameterTypes()[0]);
			MethodType  implMethod =   MethodType.methodType(Object.class,Object.class,Object.class);
			MethodHandle methodHandle = lookup.unreflect(method);
			CallSite operate = LambdaMetafactory.metafactory(lookup, "apply", invokedType, implMethod, methodHandle,
					returnType);
			BiFunction sp = (BiFunction) operate.getTarget().invoke();
		   return sp.apply(instance,value);  
		} catch (Throwable e) {
			throw BizLogger.error(e);
		}
	}
}
